1 Sobre o banco de dados

Este é um conjunto de dados público de comércio eletrônico brasileiro de pedidos feitos na Olist Store .

  1. 100 mil pedidos de 2016 a 2018 feitos em vários mercados no Brasil.
  2. Seus recursos permitem visualizar um pedido de várias dimensões
    1. Status do pedido
    2. Preço
    3. Pagamento
    4. Desempenho do frete
    5. Localização do cliente
    6. Atributos do produto
    7. Revisões escritas pelos clientes.
  3. geolocalização que relaciona os códigos postais brasileiros às coordenadas lat/lng.
  4. dados do funil de marketing São dados comerciais reais e foram anonimizados.

Em resumo o conjunto de dados contém informações sobre pedidos, produtos adquiridos, review e comentários de clientes.

2 Contexto

Esse conjunto de dados foi fornecido pela Olist, a maior loja de departamentos dos mercados brasileiros. O Olist conecta pequenas empresas de todo o Brasil a canais sem problemas e com um único contrato. Esses comerciantes podem vender seus produtos através da Olist Store e enviá-los diretamente aos clientes usando os parceiros de logística da Olist.

Depois que um cliente compra o produto na Olist Store, um vendedor é notificado para atender a esse pedido. Depois que o cliente recebe o produto ou a data estimada de entrega é vencida, ele recebe uma pesquisa de satisfação por e-mail, onde pode anotar a experiência da compra e anotar alguns comentários.

Atenção
  • Um pedido pode ter vários itens.
  • Cada item pode ser atendido por um vendedor distinto.
  • Todo o texto que identifica lojas e parceiros foi substituído pelos nomes das grandes casas de Game of Thrones.

3 Esquema de dados

Os dados são divididos em vários conjuntos de dados para melhor compreensão e organização. Consulte o seguinte esquema de dados ao trabalhar com ele:

A seguir a base de dados será descrita com o objetivo de procurar insights relevantes; Para realizar as análises será necessário unir as informações presentes nas planilhas correspondentes.Estas informações podem ser combinados utilizando os IDs como mostra o esquema acima.

4 Acessando e combinando os datasets

import pandas as pd
import pyodbc
import sys

print('Python: ' + sys.version.split('|')[0])
## Python: 3.7.6 (default, Jan  8 2020, 20:23:39) [MSC v.1916 64 bit (AMD64)]
print('Pandas: ' + pd.__version__)
## Pandas: 1.0.4
print('pyODBC: ' + pyodbc.version)
## pyODBC: 4.0.0-unsupported
import numpy as np
import pandas as pd 
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns
import datetime
import scipy.stats as stats
import matplotlib.style as style
style.use('classic')
sns.set_style('whitegrid')

# parameters
server = 'desafio-nuvem.database.windows.net' 
database = 'desafio-ia' 
username = 'markov' 
password = 'prypiatRedSky1986' 

# create the connection
string_conexao='Driver={SQL Server};Server='+server+';Database='+database+';UID='+username+';PWD='+ password
conn = pyodbc.connect(string_conexao)


sql = """
SELECT *
FROM olist_order_reviews_dataset

"""
reviews = pd.read_sql(sql, conn)
#########################################################
sql = """

SELECT *
FROM olist_orders_dataset

"""

orders = pd.read_sql(sql, conn)

df = reviews.merge(orders, on='order_id', how='outer')
del reviews
del orders

sql = """

SELECT *
FROM olist_order_items_dataset

"""
items = pd.read_sql(sql, conn)

df = df.merge(items, on='order_id', how='left')
del items

# READING DATABASES
############################################################
# query db
sql = """

SELECT *
FROM olist_customers_dataset

"""
customers = pd.read_sql(sql, conn)

df = df.merge(customers, on='customer_id', how='outer')
del customers

###########################################################
sql = """

SELECT *
FROM olist_products_dataset

"""
product = pd.read_sql(sql, conn)

df = df.merge(product, on='product_id', how='outer')
del product
#########################################################
sql = """

SELECT *
FROM olist_order_payments_dataset

"""
payments = pd.read_sql(sql, conn)
df = df.merge(payments, on='order_id', how='outer')
del payments
###########################################################
sql = """

SELECT *
FROM olist_sellers_dataset

"""
seller = pd.read_sql(sql, conn)

df = df.merge(seller, on='seller_id', how='outer')
del seller

5 1. Conhecendo a base de dados combinada

def exploraBase(df, pred=None): 
    obs = df.shape[0]
    types = df.dtypes
    counts = df.apply(lambda x: x.count())
    uniques = df.apply(lambda x: [x.unique()])
    nulls = df.apply(lambda x: x.isnull().sum())
    distincts = df.apply(lambda x: x.unique().shape[0])
    missing_ration = (df.isnull().sum()/ obs) * 100
    skewness = df.skew()
    kurtosis = df.kurt() 
    print('Data shape:', df.shape)
    
    if pred is None:
        cols = ['types', 'counts', 'distincts', 'nulls', 'missing ration', 'uniques', 'skewness', 'kurtosis']
        str = pd.concat([types, counts, distincts, nulls, missing_ration, uniques, skewness, kurtosis], axis = 1)

    else:
        corr = df.corr()[pred]
        str = pd.concat([types, counts, distincts, nulls, missing_ration, uniques, skewness, kurtosis, corr], axis = 1, sort=False)
        corr_col = 'corr '  + pred
        cols = ['types', 'counts', 'distincts', 'nulls', 'missing_ration', 'uniques', 'skewness', 'kurtosis', corr_col ]
    
    str.columns = cols
    dtypes = str.types.value_counts()
    print('___________________________\nData types:\n',str.types.value_counts())
    print('___________________________')
    return str

print("A base de dados contém", df.shape[0], "linhas e", df.shape[1], "colunas.")
## A base de dados contém 119151 linhas e 39 colunas.

6 Tratando valores ausentes


def missing_percentage(df):
    """This function takes a DataFrame(df) as input and returns two columns, total missing values and total missing values percentage"""
    ## the two following line may seem complicated but its actually very simple. 
    total = df.isnull().sum().sort_values(ascending = False)[df.isnull().sum().sort_values(ascending = False) != 0]
    percent = round(df.isnull().sum().sort_values(ascending = False)/len(df)*100,2)[(df.isnull().sum().sort_values(ascending = False)/len(df)*100) != 0]
    return pd.concat([total, percent], axis=1, keys=['Total','Percent'])

missing_percentage(df)
##                                 Total  Percent
## review_comment_title           104964    88.09
## review_comment_message          67908    56.99
## order_delivered_customer_date    3421     2.87
## product_description_lenght       2542     2.13
## product_category_name            2542     2.13
## product_name_lenght              2542     2.13
## product_photos_qty               2542     2.13
## order_delivered_carrier_date     2086     1.75
## product_weight_g                  853     0.72
## product_width_cm                  853     0.72
## product_height_cm                 853     0.72
## product_length_cm                 853     0.72
## seller_state                      836     0.70
## seller_zip_code_prefix            836     0.70
## seller_city                       836     0.70
## price                             833     0.70
## order_item_id                     833     0.70
## product_id                        833     0.70
## seller_id                         833     0.70
## shipping_limit_date               833     0.70
## freight_value                     833     0.70
## order_approved_at                 177     0.15
## payment_value                       3     0.00
## payment_sequential                  3     0.00
## payment_type                        3     0.00
## payment_installments                3     0.00

review_comment_title e review_comment_message possuem muitos valores ausentes, assim para tratar os valores ausentes será conveniente removêla. Se necessário poderá ser agregada ao datasset posteriormente carregando o dataset review. Após remover estas colunas, observe que a taxa de valores ausentes é pequena, e considerando o tamanho da base de dados, iremos remover as inhas contendo valores ausentes.

df.drop(['review_comment_title','review_comment_message'],axis=1,inplace=True)
df.dropna(inplace=True)
missing_percentage(df)
## Empty DataFrame
## Columns: [Total, Percent]
## Index: []
print("A base de dados contém", df.shape[0], "linhas e", df.shape[1], "colunas.")
## A base de dados contém 114070 linhas e 37 colunas.
detalhes = exploraBase(df)
## Data shape: (114070, 37)
## ___________________________
## Data types:
##  object     23
## float64    14
## Name: types, dtype: int64
## ___________________________
detalhes
##                                  types  counts  ...   skewness    kurtosis
## review_id                       object  114070  ...        NaN         NaN
## order_id                        object  114070  ...        NaN         NaN
## review_score                   float64  114070  ...  -1.326437    0.392062
## review_creation_date            object  114070  ...        NaN         NaN
## review_answer_timestamp         object  114070  ...        NaN         NaN
## customer_id                     object  114070  ...        NaN         NaN
## order_status                    object  114070  ...        NaN         NaN
## order_purchase_timestamp        object  114070  ...        NaN         NaN
## order_approved_at               object  114070  ...        NaN         NaN
## order_delivered_carrier_date    object  114070  ...        NaN         NaN
## order_delivered_customer_date   object  114070  ...        NaN         NaN
## order_estimated_delivery_date   object  114070  ...        NaN         NaN
## order_item_id                  float64  114070  ...   7.605035  104.447846
## product_id                      object  114070  ...        NaN         NaN
## seller_id                       object  114070  ...        NaN         NaN
## shipping_limit_date             object  114070  ...        NaN         NaN
## price                          float64  114070  ...   7.964607  123.044503
## freight_value                  float64  114070  ...   5.577027   58.940100
## customer_unique_id              object  114070  ...        NaN         NaN
## customer_zip_code_prefix        object  114070  ...   0.783798   -0.785096
## customer_city                   object  114070  ...        NaN         NaN
## customer_state                  object  114070  ...        NaN         NaN
## product_category_name           object  114070  ...        NaN         NaN
## product_name_lenght            float64  114070  ...  -0.909478    0.157028
## product_description_lenght     float64  114070  ...   2.008805    4.926617
## product_photos_qty             float64  114070  ...   1.902794    4.756235
## product_weight_g               float64  114070  ...   3.586914   16.120781
## product_length_cm              float64  114070  ...   1.733799    3.628309
## product_height_cm              float64  114070  ...   2.242495    7.284696
## product_width_cm               float64  114070  ...   1.705236    4.560173
## payment_sequential             float64  114070  ...  15.140699  311.804745
## payment_type                    object  114070  ...        NaN         NaN
## payment_installments           float64  114070  ...   1.621054    2.534077
## payment_value                  float64  114070  ...  14.469459  531.907368
## seller_zip_code_prefix          object  114070  ...   1.548827    0.908670
## seller_city                     object  114070  ...        NaN         NaN
## seller_state                    object  114070  ...        NaN         NaN
## 
## [37 rows x 8 columns]

Não há mais valores ausentes em nossa base de dados, a seguir será feita uma análise a fim de tratar outliers. É necessário ter atenção em como cada variável foi reconhecida. Data por exemplo, foi lida como objeto, para utilizá-la em uma análise será necessário mudar para o tipo data.

7 Correlação entre as variáveis

numerical_feats = df.dtypes[df.dtypes != "object"].index
print("Número de variáveis numéricas: ", len(numerical_feats))
## Número de variáveis numéricas:  14
categorical_feats = df.dtypes[df.dtypes == "object"].index
print("Número de variáveis categóricas: ", len(categorical_feats))
## Número de variáveis categóricas:  23
R=df[numerical_feats].corr()
font = {'family' : 'Times New Roman' }
plt.rc('font', **font);

## Plotting heatmap. 
fig,ax=plt.subplots(figsize=(20,20))

# Generate a custom diverging colormap
n,c=R.values.shape
num_val_dif=pd.DataFrame(R.values.reshape(n*c,1)).nunique()
cmap = sns.diverging_palette(0,500, n=num_val_dif)

sns.heatmap(R, cmap=cmap, annot=True,annot_kws={"size": 10}, center = 0,square=True, 
            linewidths=.5, cbar_kws={'shrink': .5,'orientation': 'vertical'},ax=ax);

# Gire os rótulos dos marcadores e defina seu alinhamento. 
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");

ax.figure.axes[-1].yaxis.label.set_size(12)
ax.tick_params(labelsize=12)
ax.figure.subplots_adjust(left = 0.3) # change 0.3 to suit your needs
plt.show()

8 Outliers

tratar=list(numerical_feats)
fig,ax=plt.subplots(figsize=(8,5))
sns.boxplot(x="variable", y="value", data=pd.melt(df[tratar].drop('product_weight_g',axis=1)),ax=ax)
    # Gire os rótulos dos marcadores e defina seu alinhamento. 
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show()

8.1 Escore IQR:

O gráfico boxplot usa o método IQR para exibir dados e valores discrepantes (formato dos dados), mas para obter uma lista de valores discrepantes identificados, precisamos usar a fórmula matemática e recuperar os dados discrepantes.

Q1 = df[tratar].quantile(0.25)
Q3 = df[tratar].quantile(0.75)
IQR = Q3 - Q1
print(IQR)
## review_score                     1.00
## order_item_id                    0.00
## price                           93.10
## freight_value                    8.11
## product_name_lenght             15.00
## product_description_lenght     638.00
## product_photos_qty               2.00
## product_weight_g              1500.00
## product_length_cm               20.00
## product_height_cm               12.00
## product_width_cm                15.00
## payment_sequential               0.00
## payment_installments             3.00
## payment_value                  128.42
## dtype: float64

8.2 Z-Score:

O escore Z é o número sinalizado de desvios padrão pelo qual o valor de uma observação ou ponto de dados está acima do valor médio do que está sendo observado ou medido.

from scipy import stats
import numpy as np
z = np.abs(stats.zscore(df[tratar]))
print(z)
## [[0.04941372 0.2808995  0.35629685 ... 0.13224882 1.81673336 0.84592927]
##  [0.04941372 1.14130226 0.35629685 ... 0.13224882 1.81673336 0.84592927]
##  [0.6870147  0.2808995  0.35629685 ... 0.13224882 0.01927146 0.12445682]
##  ...
##  [0.6870147  0.2808995  0.21990905 ... 0.13224882 0.34022092 0.27731315]
##  [2.258699   0.2808995  3.45388327 ... 0.13224882 1.81673336 2.23788259]
##  [0.6870147  0.2808995  0.22471795 ... 0.13224882 0.01927146 0.10972676]]
z.shape
## (114070, 14)

df_z=pd.DataFrame(z,columns=tratar)
# verificando a presença de outliers em cada variável
for var in tratar:
    n_outl=len(df_z[var][df_z[var]>3])
    if n_outl!=0:
        print('Existem {} outiliers na variável {}'.format(n_outl,var))
## Existem 1880 outiliers na variável order_item_id
## Existem 2029 outiliers na variável price
## Existem 2095 outiliers na variável freight_value
## Existem 561 outiliers na variável product_name_lenght
## Existem 2780 outiliers na variável product_description_lenght
## Existem 1612 outiliers na variável product_photos_qty
## Existem 2935 outiliers na variável product_weight_g
## Existem 2221 outiliers na variável product_length_cm
## Existem 2770 outiliers na variável product_height_cm
## Existem 1749 outiliers na variável product_width_cm
## Existem 975 outiliers na variável payment_sequential
## Existem 400 outiliers na variável payment_installments
## Existem 1755 outiliers na variável payment_value
del df_z
sns.distplot(df.price,kde=False)

Agora podemos remover ou filtrar os outliers e obter os dados limpos. Isso pode ser feito com apenas um código de linha, pois já calculamos o Z-score.

df=df[(z < 3).all(axis=1)]
sns.distplot(df.price,kde=False)

Assim como o Z-score, podemos usar o IQR calculado anteriormente para filtrar os valores extremos

#df = df[~((df < (Q1 - 1.5 * IQR)) |(df > (Q3 + 1.5 * IQR))).any(axis=1)]

Agora que os outliers foram removidos podemos começar a buscar alguns insights e ajustar um modelo para predição.

print("A base de dados contém", df.shape[0], "linhas e", df.shape[1], "colunas.")
## A base de dados contém 97423 linhas e 37 colunas.

9 Insights para a área de marketing

Quais estados concentram o maior número de vendas?

# nunique: Contar observações distintas sobre o eixo solicitado.
n_estados = df["customer_state"].nunique()
df2 = df.groupby(['customer_state'])[['customer_id']].nunique().sort_values('customer_id',ascending=False)
df2=df2.head(10)
print("São ",n_estados,"estados na base de dados. Os 10 estados com maior número de clientes são:")
## São  27 estados na base de dados. Os 10 estados com maior número de clientes são:
df2.reset_index(level=0, inplace=True)
df2.columns=['Estado','N_Clientes']
df2
##   Estado  N_Clientes
## 0     SP       35270
## 1     RJ       10679
## 2     MG        9924
## 3     RS        4629
## 4     PR        4278
## 5     SC        3042
## 6     BA        2803
## 7     DF        1823
## 8     ES        1753
## 9     GO        1687

Para uma melhor visualização dos resultados veja o gráfico a seguir:

import numpy as np
pal = sns.color_palette("Blues_d", len(df2))
rank = df2.N_Clientes.argsort().argsort() 

fig,ax=plt.subplots(figsize=(8,5))

g=sns.barplot(x='Estado',y='N_Clientes',data=df2,
              palette=np.array(pal[::-1])[rank],
              ci = None,ax=ax)

for p in g.patches:
    g.annotate(format(p.get_height(), '.0f'), (p.get_x() + p.get_width() / 2., p.get_height()), ha = 'center', va = 'center', xytext = (0, 10), textcoords = 'offset points')

    # Gire os rótulos dos marcadores e defina seu alinhamento. 
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.set_ylabel('Número de Clientes')
plt.tight_layout();
plt.show()

O número de cidades é muito grande, e até mesmo o de estados para plotar todas estas informações em um gráfico de barras ou tabela contendo o total de clientes por estado por exemplo. Uma forma de exibir estes resultados de forma sintetizada, é utilizar as informações de geolocalização, e plotar como um pequeno ponto, cada cliente em um maapa. Isso nos dará uma ótima vizualização de como os clientes estão distribuídos ao longo do país.

# Obter os valores de latitude e longitude de cada cliente para plotar
import folium
map = folium.Map(location=[-15.7797200,-47.9297200], zoom_start=4,tiles='openstreetmap')
map
## <folium.folium.Map object at 0x000000000D3E3E08>

Quais são as categorias de produtos mais vendidas?

df2=df.copy()
df2['Pontuacao']=1
df2 = df2.groupby(['product_category_name'])[['Pontuacao']].sum().sort_values('Pontuacao',ascending=False)
n_categorias = df['product_category_name'].nunique()
print("São ",n_categorias,"categorias na base de dados. As 10 categorias mais vendidas são:")
## São  73 categorias na base de dados. As 10 categorias mais vendidas são:
df2.reset_index(level=0, inplace=True)
df2.columns=['product_category_name','N_Vendas']
df2.head(10)
##     product_category_name  N_Vendas
## 0         cama_mesa_banho     11032
## 1            beleza_saude      8597
## 2  informatica_acessorios      7326
## 3           esporte_lazer      7254
## 4        moveis_decoracao      7004
## 5   utilidades_domesticas      5783
## 6      relogios_presentes      5313
## 7               telefonia      4335
## 8      ferramentas_jardim      3969
## 9              brinquedos      3691
pal = sns.color_palette("Greens_d", len(df2))
rank = df2.N_Vendas.argsort().argsort() 

fig,ax=plt.subplots(figsize=(8,5))

g=sns.barplot(x='product_category_name',y='N_Vendas',data=df2,
              palette=np.array(pal[::-1])[rank],
              ci = None,ax=ax)
    # Gire os rótulos dos marcadores e defina seu alinhamento. 
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.set_ylabel('Número de itens Vendidos')
ax.get_xaxis().set_visible(False)
plt.tight_layout();
plt.show()

n=10
pal = sns.color_palette("Blues_d", len(df2.head(n)))
rank = df2.head(n).N_Vendas.argsort().argsort() 

fig,ax=plt.subplots(figsize=(8,5))

g=sns.barplot(x='product_category_name',y='N_Vendas',data=df2.head(n),
              palette=np.array(pal[::-1])[rank],
              ci = None,ax=ax)

for p in g.patches:
    g.annotate(format(p.get_height(), '.0f'), (p.get_x() + p.get_width() / 2., p.get_height()), ha = 'center', va = 'center', xytext = (0, 10), textcoords = 'offset points')

    # Gire os rótulos dos marcadores e defina seu alinhamento. 
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.set_ylabel('Número de itens Vendidos')
plt.tight_layout();
plt.show()

Satisfação dos clientes: o review dos clientes tem sido favorável?

df2=df.copy()
df2['Pontuacao']=1
df2 = df2.groupby(['review_score'])[['Pontuacao']].sum().sort_values('Pontuacao',ascending=False)
df2.reset_index(level=0, inplace=True)
df2.columns=['Review_Score','N_Avaliacoes']
df2
##    Review_Score  N_Avaliacoes
## 0           5.0         56225
## 1           4.0         18818
## 2           1.0         10837
## 3           3.0          8225
## 4           2.0          3318
pal = sns.hls_palette(5, l=.3, s=.8)
rank = df2.N_Avaliacoes.argsort().argsort() 

fig,ax=plt.subplots(figsize=(8,5))

g=sns.barplot(x='Review_Score',y='N_Avaliacoes',data=df2,
              palette=np.array(pal[::-1])[rank],
              ci = None,ax=ax)

for p in g.patches:
    g.annotate(format(p.get_height(), '.0f'), (p.get_x() + p.get_width() / 2., p.get_height()), ha = 'center', va = 'center', xytext = (0, 10), textcoords = 'offset points')

    # Gire os rótulos dos marcadores e defina seu alinhamento. 
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.set_ylabel(' ')
plt.tight_layout();
plt.show()

Número de vendas e reviews ao longo do tempo.

Para iniciar, é necessário transformar a coluna com as datas em datetime.

col_datas=['review_creation_date','order_purchase_timestamp','order_delivered_carrier_date','order_delivered_customer_date',
           'order_estimated_delivery_date','order_approved_at','shipping_limit_date'
   ]
df[col_datas]=df[col_datas].apply(pd.to_datetime)
df['Data'] = df['review_creation_date'].apply(lambda x: x.strftime('%Y-%m'))
df['Data']=df['Data'].apply(pd.to_datetime)
df2=df.copy()
# df2.dropna(inplace=True)
df2['Pontuacao']=1
df2=df2.groupby(['review_score','Data'])[['Pontuacao']].sum()
df2.reset_index(level=0, inplace=True)
df2.reset_index(level=0, inplace=True)

keys=[i for i in range(1,6)]
values=['Nota {}'.format(i) for i in range(1,6)]
df2['review_score'] = df2['review_score'].map(dict(zip(keys, values)))
df2=df2[(df2['Data'] > '2014-01-01') & (df2['Data'] < '2018-08-01')]
# style.use('default')
fig,ax=plt.subplots(figsize=(8,5))

# adiciona um título e os rótulos dos eixos 

## Plot
sns.lineplot(x="Data", y='Pontuacao',hue='review_score',data=df2,ax=ax)
sns.scatterplot(x="Data", y='Pontuacao',hue='review_score',style='review_score',
                data=df2,ax=ax,legend=False,clip_on=False);

ax.set_xlabel('Data de criação do review')
ax.set_ylabel('Número de Avaliações')

plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
l = ["upper left", "lower left", "center right"];
ax.legend(loc=l[0], bbox_to_anchor=(1,1));
plt.ylim(0);
plt.tight_layout();
plt.show ()

df['Data'] = df['order_approved_at'].apply(lambda x: x.strftime('%Y-%m'))
df['Data']=df['Data'].apply(pd.to_datetime)
df2=df.copy()
df2['Pontuacao']=1
df2=df2.groupby(['Data'])[['Pontuacao']].sum()
df2.reset_index(level=0, inplace=True)
df2=df2[(df2['Data'] > '2014-01-01') & (df2['Data'] < '2018-07-01')]
fig,ax=plt.subplots(figsize=(8,5))

# adiciona um título e os rótulos dos eixos 
### Plot
ax.plot(df2['Data'], df2['Pontuacao'],'o-',color='green',clip_on=False)
ax.set_xlabel('')
ax.set_ylabel('Número de Itens Vendidos')
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show ()

from statistics import median
mean = df["payment_value"].mean()
mediana = df["payment_value"].median()
std = df["payment_value"].std()
text1 = '$\mu=$' + str(round(mean,2))
text2 = '$mediana=$' + str(round(mediana,2))
text3 = '$\sigma=$' +str(round(std,2))
text = text1 + "\n" + text2 + "\n" + text3


fig,ax=plt.subplots(figsize=(10,7))
df.hist(column = "payment_value", bins = 100,color='#2F0B92', figsize=(15,8), rwidth=0.9,ax=ax);
plt.axvline(mediana, color='r', linestyle='--')
ax.text(mediana+10, 6000, text, fontsize=12)
ax.set_xlabel("Valor do pagamento")
ax.set_ylabel("Counts")
ax.set_title("Histograma dos valores de pagamentos")
plt.tight_layout();
plt.show()

df2=df.sort_values('Data', ascending=True)
df2=df2.groupby(['Data'])[['payment_value']].sum()
df2['cum_sum'] = df2['payment_value'].cumsum()
df2.reset_index(level=0, inplace=True)
df2=df2[(df2['Data'] > '2014-01-01') & (df2['Data'] < '2018-08-01')]
df2['cum_sum']=df2['cum_sum']/1000000
fig,ax=plt.subplots(figsize=(8,5))

## Plot
plt.plot(df2['Data'], df2['cum_sum'],'o-',clip_on=False)
ax.fill_between(df2['Data'], df2['cum_sum'], 0,color='#b3b3ff')
ax.set_xlabel(' ')
ax.set_ylabel('Soma acumulada de valor de pagamento (Milhões)')
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show ()

Além de produtos de boa qualidade para garantir a satisfação dos clientes é essencial que o processo de compra até a entrega seja ágil. Portanto o tempo gasto entre a data da compra e a entrega é uma variável muito importante que deve ser considerada para predizer a satisfação. O preço dos produtos também é excencial, afinal os clientes ficam satisfeitos quando julgam o valor pago pelo produto justo ou vantajoso.

A seguir listamos as variáveis disponíveis no dataset que contém características do produto, data da compra e entrega, valor do frete.

  • Tempo de entrega
    • Tempo_entrega: Dias gastos da data de compra do pedido order_purchase_timestamp até a entrega ao cliente order_delivered_customer_date.
    • Tempo_entrega_previsto: Número de dias entre a data de compra do pedido order_purchase_timestamp até a data prevista de entrega order_estimated_delivery_date.
  • Frete:
    • Valor do frete: freight_value.
  • Cracterísticas do produto:
    • Quantidade de fotos do produto: product_photos_qty.
df['Tempo_entrega']=df.order_delivered_customer_date-df.order_purchase_timestamp
df['Tempo_entrega']=df['Tempo_entrega'].dt.days
df['Tempo_entrega_previsto']=df.order_estimated_delivery_date-df.order_purchase_timestamp
df['Tempo_entrega_previsto']=df['Tempo_entrega_previsto'].dt.days
df.iloc[:,-2:].head()
##    Tempo_entrega  Tempo_entrega_previsto
## 0              6                      21
## 1              6                      21
## 2             14                      31
## 3             19                      31
## 4             17                      23
fig,ax=plt.subplots(figsize=(8,5))
sns.boxplot(x="review_score", y="Tempo_entrega", data=df,ax=ax)
# sns.stripplot(x="review_score", y="Tempo_entrega", data=df,ax=ax)
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show()

Como pode ser observado nos boxplots acima, o tempo de entrega afeta o review. Apesar de terem a mediana próxima dos produtores com maior review, eles apresentam maior variação no tempo de entrega e maior probabilidade de atraso na entrega dos produtos.

fig,ax=plt.subplots(figsize=(8,5))
sns.boxplot(x="review_score", y="freight_value", data=df,ax=ax)
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show()

fig, ax = plt.subplots(figsize=(9, 8), subplot_kw=dict(aspect="equal"))
explode = (0.1, 0, 0, 0)
colors = ['#f45a5a', '#449dfc', '#93f96d', '#f9c86d']
legend = ["Credit Card", "Boleto", "Voucher", "Debit Card"]

p = df["payment_type"][df["payment_type"] != "not_defined"].value_counts()
p.plot(kind="pie", legend=False, labels=None, startangle=0, explode=explode, autopct='%1.0f%%', pctdistance=0.6, shadow=True, textprops={'weight':'bold', 'fontsize':16}, 
       colors=colors, ax=ax)
ax.legend(legend, loc='best', shadow=True, prop={'weight':'bold', 'size':12}, bbox_to_anchor=(0.6, 0, 0.5,1))
plt.title("Métodos de pagamento", fontweight='bold', size=16)
plt.tight_layout();
plt.ylabel("");
plt.show()

10 Agrupando os produtos: k-means

# Imports
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from scipy.spatial.distance import cdist, pdist
import warnings

Iremos agrupar os items vendidos baseado em suas características a fim de verificar quais categorias se encontram em cada cluster. Com este agrupamento, poderemos observar também se as características do produto afeta o review do cliente. Para isto iremos utilizar o algoritmo de k-means. Este processo agrupará os produtos minimizando a similaridade dentro dos clusters e maximizando a similaridae entre produtos de clusters distintos.

df2=df[['product_id','product_category_name', 'product_name_lenght',
       'product_description_lenght', 'product_photos_qty', 'product_weight_g',
       'product_length_cm', 'product_height_cm', 'product_width_cm']]
df2=df2.groupby(['product_id','product_category_name']).mean()

Encontrar o melhor número de clusters para esse conjunto de dados

Para o método de agrupamento k-means, a abordagem mais comum para responder a essa pergunta é o chamado método de elbow (cotovelo). Isso envolve executar o algoritmo várias vezes em um loop, com um número crescente de opções de cluster e, em seguida, plotar uma pontuação de cluster como uma função do número de clusters.

A pontuação é, em geral, alguma forma de distância entre clusters em relação à distância interna do cluster .

# Aplica redução de dimensionalidade
pca = PCA(n_components = 2).fit_transform(df2)
pca=pd.DataFrame(pca,columns=['Componente 1','Componente 2'])

# Determinando um range de K
k_range = range(1,12)
# Aplicando o modelo K-Means para cada valor de K
k_means_var = [KMeans(n_clusters = k).fit(pca) for k in k_range]

# Ajustando o centróide do cluster para cada modelo
centroids = [X.cluster_centers_ for X in k_means_var]

# Calculando a distância euclidiana de cada ponto de dado para o centróide
k_euclid = [cdist(pca, cent, 'euclidean') for cent in centroids]
dist = [np.min(ke, axis = 1) for ke in k_euclid]

# Soma dos quadrados das distâncias dentro do cluster
soma_quadrados_intra_cluster = [sum(d**2) for d in dist]

# Soma total dos quadrados
soma_total = sum(pdist(pca)**2)/pca.shape[0]

# Soma dos quadrados entre clusters
soma_quadrados_inter_cluster = soma_total - soma_quadrados_intra_cluster
# Curva de Elbow
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(k_range, soma_quadrados_inter_cluster/soma_total * 100, 'b*-')
ax.set_ylim((0,100));
plt.grid(True);
plt.xlabel('Número de Clusters');
plt.ylabel('Percentual de Variância Explicada');
plt.title('Variância Explicada x Valor de K');
plt.tight_layout();
plt.show()

# Criando um modelo com K = 5
modelo_v1 = KMeans(n_clusters = 5)
modelo_v1.fit(pca)
## KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
##        n_clusters=5, n_init=10, n_jobs=None, precompute_distances='auto',
##        random_state=None, tol=0.0001, verbose=0)
fig,ax=plt.subplots(figsize=(8,5))
# Plot dos centróides
#Plot dos Clusters
colors = ["black", "green", "blue","yellow","orange"]
pca2=pca
pca2['cluster'] = modelo_v1.labels_
pca2['color'] = pca['cluster'].map(lambda p: colors[p])
pca2.plot(kind="scatter", x="Componente 1", y="Componente 2",c = pca2['color'],alpha=0.8,s=100,ax=ax)
centroids = modelo_v1.cluster_centers_
inert = modelo_v1.inertia_
plt.scatter(centroids[:, 0], centroids[:, 1], marker = 'x', s = 169, linewidths = 3, color = 'r', zorder = 8)
plt.xticks(());
plt.yticks(());
plt.xlabel('Componente 1');
plt.ylabel('Componente 2');
plt.tight_layout();
plt.show()

df2['cluster'] = modelo_v1.labels_
df2['review_score']=df.groupby(['product_id','product_category_name'])['review_score'].mean()


fig,ax=plt.subplots(figsize=(8,5))
sns.boxplot(x="cluster", y="review_score", data=df2,ax=ax)
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show()

11 Prevendo review de clientes: Random Forest

Nesta etapa ajustaremos um modelo a fim de predizer o review do cliente baseado em algumas informações. Para isso usaremos a técnica de classificação random forest.

Inicialmente o conjunto de dados será dividido em conjunto de trinamento e conjunto de testes. 90% dos clientes serão incluidos no modelo de treinamento, e 10% serão adicionados ao dataset de teste. Desta forma após ajustar o molelo, será possível verificar seu desempenho quando utilizamos um atributos de entrada pertencentes a indivíduos fora do conjunto de treino.

### Modelo
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix
from sklearn.model_selection  import  train_test_split
X=df[['customer_id', 'price', 'freight_value', 'product_name_lenght','product_description_lenght',
 'product_photos_qty', 'product_weight_g','product_length_cm', 'product_height_cm',
 'product_width_cm', 'payment_value',  'Tempo_entrega', 'Tempo_entrega_previsto']]
X.set_index('customer_id',inplace=True);
y=df.review_score
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.10)
X_train.shape, X_test.shape
## ((87680, 12), (9743, 12))
# Random Forest
rf_classifier = RandomForestClassifier(n_estimators = 10, criterion = 'entropy', random_state = 0)
rf_classifier.fit(X_train, y_train)
## RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None,
##                        criterion='entropy', max_depth=None, max_features='auto',
##                        max_leaf_nodes=None, max_samples=None,
##                        min_impurity_decrease=0.0, min_impurity_split=None,
##                        min_samples_leaf=1, min_samples_split=2,
##                        min_weight_fraction_leaf=0.0, n_estimators=10,
##                        n_jobs=None, oob_score=False, random_state=0, verbose=0,
##                        warm_start=False)
y_pred_rf = rf_classifier.predict(X_test)

Para avaliar o deempenho do modelo iremos utilizar uma matriz de confusão.

#confusion_matrix
cnf_matrix = confusion_matrix(y_test,y_pred_rf)
p = sns.heatmap(pd.DataFrame(cnf_matrix), annot=True, cmap="YlGnBu" ,
                fmt='g',yticklabels=[1,2,3,4,5],xticklabels=[1,2,3,4,5])
plt.title('Confusion matrix', y=1.1);
plt.ylabel('True label');
plt.xlabel('Predicted label');
plt.show()

p=sns.heatmap(cnf_matrix/np.sum(cnf_matrix), annot=True, fmt='.2%', cmap="YlGnBu",
              yticklabels=[1,2,3,4,5],xticklabels=[1,2,3,4,5])
plt.title('Confusion matrix', y=1.1);
plt.ylabel('True label');
plt.xlabel('Predicted label');
plt.show()

 

Trabalho de Cristiano Ferreira de Oliveira

cristiano2132.ufv@gmail.com